﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml;

namespace InfFront.InfoViews.Base
{
    public class BaseSettingsManager: IVisualSettingsManager
    {
        #region Private Variables
        protected static readonly PropertyDescriptorCollection properties;

        private SettingsDb database;
        private SettingsAccessor rootAccessor;
        #endregion Private Variables

        public const string COLUMN_ORDER_KEY = "ColumnOrder";
        public const string LAST_SELECTED_SOURCE_KEY = "LastSelectedInfoSourcePath";
        public const string FORMATTED_PAGE_DESCRIPTION_KEY = "FormattedPageDescription";

        static BaseSettingsManager()
        {
            properties = new PropertyDescriptorCollection(new PropertyDescriptor[] { });

            RegisterProp<int>(COLUMN_ORDER_KEY, -1);
            RegisterProp<string[]>(LAST_SELECTED_SOURCE_KEY, null);
            RegisterProp<RawString>(FORMATTED_PAGE_DESCRIPTION_KEY, new RawString());
        }

        public BaseSettingsManager()
        {
            database = new SettingsDb();
            SettingsAccessor defaultRoot = null;
            if (database.LoadFrom(SettingsDb.DefaultAppDataPath))
            {
                var defDb = new SettingsDb();
                if (defDb.LoadFrom(SettingsDb.DefaultCommonPath))
                    defaultRoot = new SettingsAccessor(defDb.Root, this);
            }
            else if (!database.LoadFrom(SettingsDb.DefaultCommonPath))
                database.SetEmpty();
            rootAccessor = new SettingsAccessor(database.Root, this, defaultRoot);
        }

        public SettingsAccessor RootAccessor
        {
            get { return rootAccessor; }
        }

        public void SaveDatabase()
        {
            database.SaveTo(SettingsDb.DefaultAppDataPath);
        }

        protected static void RegisterProp<T>(string name, T defValue)
        {
            var p = new SettingPropertyDescriptor(name, typeof(T), defValue);
            properties.Add(p);
        }

        protected static void RegisterEnumProp<T>(string name, T defValue)
        {
            var p = new EnumSettingPropertyDescriptor(name, typeof(T), defValue);
            properties.Add(p);
        }

        internal PropertyDescriptorCollection Properties
        {
            get { return properties; }
        }

        internal protected SettingPropertyDescriptor FindProp(string propName)
        {
            return (SettingPropertyDescriptor)properties.Find(propName, false);
        }

        #region IVisualSettingsManager Members

        object IVisualSettingsManager.AppGlobals
        {
            get { return null; }
        }

        #endregion
    }

    public class SettingsAccessor : INotifyPropertyChanged, ICustomTypeDescriptor, IVisualSettingsAccessor
    {
        #region Private Variables
        private string name;
        private SettingsAccessor parent = null;
        private BaseSettingsManager manager = null;
        private ISettingsDbNode dbNode = null;
        private Dictionary<SettingPropertyDescriptor, CachedValue> values = null;
        private Dictionary<string, WeakReference> children = null;
        private HashSet<SettingPropertyDescriptor> inheritedProps = null;
        private SettingsAccessor defaultAccessor = null;
        private bool isFrozen = false;
        #endregion Private Variables

        #region Constrution/Initialization

        public SettingsAccessor(ISettingsDbNode dbNode, BaseSettingsManager manager, SettingsAccessor defaultAccessor)
        {
            this.name = string.Empty;
            this.dbNode = dbNode;
            this.manager = manager;
            this.defaultAccessor = defaultAccessor;
        }

        public SettingsAccessor(ISettingsDbNode dbNode, BaseSettingsManager manager)
            : this(dbNode, manager, null)
        {
        }

        public SettingsAccessor(string name, SettingsAccessor parent, ISettingsDbNode dbNode)
        {
            this.name = name;
            this.parent = parent;
            this.dbNode = dbNode;
            this.manager = parent.manager;
            if (parent.defaultAccessor != null)
                defaultAccessor = parent.defaultAccessor.OpenChildSettings(name, false);
        }

        private void EnsureValueDict()
        {
            if (values == null)
                values = new Dictionary<SettingPropertyDescriptor, CachedValue>();
        }

        private void EnsureChildDict()
        {
            if (children == null)
                children = new Dictionary<string, WeakReference>();
        }

        private void EnsureInheritedPropDict()
        {
            if (inheritedProps == null)
                inheritedProps = new HashSet<SettingPropertyDescriptor>();
        }

        #endregion Constrution/Initialization

        public BaseSettingsManager Manager
        {
            get { return manager; }
        }

        IVisualSettingsManager IVisualSettingsAccessor.Manager 
        {
            get { return Manager; }
        }

        private SettingsAccessor OpenChildSettings(string childName, bool createMissing)
        {
            EnsureChildDict();
            WeakReference childRef;
            if (children.TryGetValue(childName, out childRef))
            {
                var child = (SettingsAccessor)childRef.Target;
                if (child != null)
                    return child;
                children.Remove(childName);
            }
            var childDbNode = dbNode.OpenChildNode(childName, createMissing);
            if (childDbNode == null)
                return null;
            var result = CreateChildAccessor(childName, childDbNode);
            children[childName] = new WeakReference(result);
            return result;
        }

        public IVisualSettingsAccessor OpenChildSettings(string childName)
        {
            return OpenChildSettings(childName, true);
        }

        protected SettingsAccessor CreateChildAccessor(string name, ISettingsDbNode dbNode)
        {
            return new SettingsAccessor(name, this, dbNode);
        }

        protected bool TryGetDbValue(SettingPropertyDescriptor prop, out object value, out DateTime modTime)
        {
            if (!dbNode.TryGetValue(prop.Name, out value, out modTime))
                return false;
            value = prop.ConvertFromDb(value);
            return true;
        }

        protected void SetDbValue(SettingPropertyDescriptor prop, object value)
        {
            if (value == null)
                dbNode.DeleteValue(prop.Name);
            else
            {
                value = prop.ConvertToDb(value);
                dbNode.SetValue(prop.Name, value);
            }
        }

        private struct CachedValue
        {
            public object Value;
            public DateTime ModTime;
        }

        private object GetValue(SettingPropertyDescriptor prop, bool searchInParentsOrDefaults, out DateTime modTime)
        {
            CachedValue cval;
            if (values != null && values.TryGetValue(prop, out cval))
            {
                modTime = cval.ModTime;
                return cval.Value;
            }
            DateTime defModTime = DateTime.MinValue;
            object defValue = null;
            if (defaultAccessor != null)
                defValue = defaultAccessor.GetValue(prop, false, out defModTime);

            // search in the db
            cval = new CachedValue();
            if ((!TryGetDbValue(prop, out cval.Value, out cval.ModTime) || defModTime >= cval.ModTime)
                && defValue != null)
            {
                cval.Value = defValue;
                cval.ModTime = defModTime;
            }

            if (cval.Value != null)
            {
                EnsureValueDict();
                values[prop] = cval;
                modTime = cval.ModTime;
                return cval.Value;
            }

            if (!searchInParentsOrDefaults)
            {
                modTime = DateTime.MinValue;
                return null;
            }

            if (parent != null)
            {
                EnsureInheritedPropDict();
                inheritedProps.Add(prop);
                return parent.GetValue(prop, true, out modTime);
            }
            modTime = DateTime.MinValue;
            return prop.DefaultValue;
        }

        public object GetValue(SettingPropertyDescriptor prop, bool searchInParentsOrDefaults)
        {
            DateTime modTime = DateTime.MinValue;
            return GetValue(prop, searchInParentsOrDefaults, out modTime);
        }

        public object GetValue(SettingPropertyDescriptor prop)
        {
            return GetValue(prop, true);
        }

        public object GetValue(string propName, bool searchInParentsOrDefaults)
        {
            return GetValue(manager.FindProp(propName), searchInParentsOrDefaults);
        }

        public object GetValue(string propName)
        {
            return GetValue(propName, true);
        }

        public void SetValue(SettingPropertyDescriptor prop, object value, bool updateDb)
        {
            if (IsFrozen)
                return;

            EnsureValueDict();
            if (value != null)
                values[prop] = new CachedValue() { ModTime = DateTime.Now, Value = value };
            else
                values.Remove(prop);
            if (updateDb)
                SetDbValue(prop, value);
            if (inheritedProps != null)
                inheritedProps.Remove(prop);
            FirePropertyChanged(prop, GetValue(prop));
        }

        public void SetValue(SettingPropertyDescriptor prop, object value)
        {
            SetValue(prop, value, true);
        }

        public void SetValue(string propName, object value, bool updateDb)
        {
            SetValue(manager.FindProp(propName), value, updateDb);
        }

        public void SetValue(string propName, object value)
        {
            SetValue(propName, value, true);
        }

        public void ResetAll()
        {
            foreach (SettingPropertyDescriptor prop in values.Keys.ToList())
            {
                SetValue(prop, null);
            }
        }

        protected void FirePropertyChanged(SettingPropertyDescriptor prop, object value)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(prop.Name);
                handler(this, args);
            }

            if (children != null)
            {
                foreach (var childItem in children)
                {
                    var child = (SettingsAccessor)childItem.Value.Target;
                    if (child == null)
                    {
                        children.Remove(childItem.Key);
                        continue;
                    }
                    if (child.inheritedProps != null && child.inheritedProps.Contains(prop))
                        child.FirePropertyChanged(prop, value);

                }
            }
        }

        public bool IsFrozen
        {
            get
            {
                if (isFrozen)
                    return true;
                if (parent != null)
                    return parent.IsFrozen;
                return false;
            }
            set
            {
                isFrozen = value;
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region ICustomTypeDescriptor Members

        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return AttributeCollection.Empty;
        }

        public string GetClassName()
        {
            return base.GetType().Name;
        }

        string ICustomTypeDescriptor.GetComponentName()
        {
            return GetClassName();
        }

        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return null;
        }

        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return null;
        }

        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return null;
        }

        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return null;
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return EventDescriptorCollection.Empty;
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return EventDescriptorCollection.Empty;
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return Manager.Properties;
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return Manager.Properties;
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        #endregion
    }

    /// <summary>
    /// PropertyDescriptor wrapping the InfoFieldField
    /// </summary>
    public class SettingPropertyDescriptor : PropertyDescriptor
    {
        // Fields
        private string name;
        private object defValue;
        private Type type;

        // Methods
        protected internal SettingPropertyDescriptor(string name, Type type, object defValue)
            : base(name, null)
        {
            this.name = name;
            this.type = type;
            this.defValue = defValue;
        }

        public object DefaultValue
        {
            get { return defValue; }
        }

        public virtual object ConvertToDb(object value)
        {
            return value;
        }

        public virtual object ConvertFromDb(object value)
        {
            return value;
        }

        public override bool CanResetValue(object component)
        {
            throw new NotSupportedException("!");
        }

        public override object GetValue(object component)
        {
            var obj = (SettingsAccessor)component;
            return obj.GetValue(this);
        }

        public override void ResetValue(object component)
        {
            throw new NotSupportedException("!");
        }

        public override void SetValue(object component, object value)
        {
            var obj = (SettingsAccessor)component;
            obj.SetValue(this, value);
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        // Properties
        public override Type ComponentType
        {
            get { return typeof(SettingsAccessor); }
        }

        public override bool IsBrowsable
        {
            get { return true; }
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override Type PropertyType
        {
            get { return type; }
        }
    }

    /////////////////////////////////////////////////////////////////////////////
    //  custom PropertyMetadatas

    internal class EnumSettingPropertyDescriptor : SettingPropertyDescriptor
    {
        public EnumSettingPropertyDescriptor(string name, Type type, object defValue)
            : base(name, type, defValue)
        {
        }

        public override object ConvertToDb(object value)
        {
            return value.ToString();
        }

        public override object ConvertFromDb(object value)
        {
            return Enum.Parse(PropertyType, (string)value);
        }
    }
}
